﻿using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Shapes;
using System.Collections.Generic;
using System.Windows.Media.Imaging;
using System.IO;
using Amercom.Silverlight.Lib;

namespace Amercom.Silverlight.Helpers.PanoramaCube
{
    /// <summary>
    /// Distort/warp a bitmap onto a 4 point polygon
    /// </summary>
    /// <example></example>
    /*
    ****************************
    * DistortImage, Silverlight version
    * By Martijn Croezen and Rob Houweling
    * This is a proof of concept version and is not at all optimized or cleaned up!
    * 
    * martijn@amercom.nl / http://whizzkid74.blogspot.com
    * rob@amercom.nl / http://web-snippets.blogspot.com
    * (c) 2008 Amercom B.V. http://www.amercom.nl
    * **************************
    * 
    *********** HISTORY **********
    * original idea and first implementation from (C) Andre Michelle 
    * http://www.andre-michelle.com
    ****************************
    * Flash version Thomas Pfeiffer - kiroukou
    * kiroukou@@gmail.com 
    ****************************
    * Adapted to silverlight by Martijn Croezen
    * http://whizzkid74.blogspot.com
    * Basically, the setup of this class is the same as Thomas' version
    * but nearly all the innards of the routines have been changed
    * most notably the setTransform, CalculateSegments and __render routines.
    */




    public class DistortImage
    {
        public double mouseX;
        public double mouseY;

        private int _SourceWidth;
        private int _SourceHeight;

        // -- skew and translation matrix
        private Matrix translateMatrix;

        private double _XMin;
        private double _XMax;
        private double _YMin;
        private double _YMax;

        private int _SegmentCount;

        private double _HorizontalSegmentLength;
        private double _VerticalSegmentLength;

        public List<UVPoint> Points;
        public List<QuadPoint> Quads;
        private Panel _targetCanvas;

        private Uri _textureUri;
        public Uri TextureUri { get { return (_textureUri); } set { _textureUri = value; } }

        private Boolean _useTextureMapping;
        public Boolean UseTextureMapping { get { return (_useTextureMapping); } set { _useTextureMapping = value; } }

        Stream _stream;
        public Boolean IsTextureLoaded = false;
        BitmapImage TextureBitmap;
        public double polygonOverlap;

        private double Width = 0;
        private double Height  = 0;

        public DistortImage(int SourceWidth, int SourceHeight, int HorizontalSegments, double polygonOverlap, Panel targetCanvas, double Width, double Height, Uri TextureUri, Boolean UseTextureMapping, BitmapImage theBitmap)
        {
            this.Width = Width;
            this.Height = Height;
            this.polygonOverlap = polygonOverlap;
            TextureBitmap = theBitmap;
            //this.Texture = Texture;
            this.UseTextureMapping = UseTextureMapping;
            Points = new List<UVPoint>();
            Quads = new List<QuadPoint>();
            // save the brush
            this.TextureUri = TextureUri;
            _SegmentCount = HorizontalSegments;
            _SourceWidth = SourceWidth;
            _SourceHeight = SourceHeight;
            _targetCanvas = targetCanvas;
            CalculateSegments();

            WebClient webClient = new WebClient();

            webClient.OpenReadCompleted += new OpenReadCompletedEventHandler(webClient_OpenReadCompleted);
            webClient.OpenReadAsync(this.TextureUri);

        }

        void webClient_OpenReadCompleted(object sender, OpenReadCompletedEventArgs e)
        {
            if (e.Error == null)
            {
                DebugLogger.debugWrite("*Texture loaded : " + e.Result.ToString());
                this._stream = e.Result;
                this.IsTextureLoaded = true;

            }
            else
            {
                DebugLogger.debugWrite("*************************************\nTexture Error : " + e.Error.ToString());
            }

        }

        public void  UpdateViewportSize(double Width, double Height)
        {
            this.Width = Width;
            this.Height = Height;
        }

        private void CalculateSegments()
        {
            Double ix;
            Double iy;

            Double w2 = _SourceWidth / 2;
            Double h2 = _SourceHeight / 2;

            _XMin = _YMin = 0;
            _XMax = _SourceWidth;
            _YMax = _SourceHeight;

            _HorizontalSegmentLength = _SourceWidth / (double)_SegmentCount;
            _VerticalSegmentLength = _SourceHeight / (double)_SegmentCount;


            // -- make Uv-like mapping coords 
            for (ix = 0; ix < _SegmentCount; ix++)
            {
                for (iy = 0; iy < _SegmentCount; iy++)
                {
                    // 1---2
                    // |\ a|
                    // | \ |
                    // |b \|
                    // 3---4
                    UVPoint p1 = makeUV((ix * _HorizontalSegmentLength), ((iy) * _VerticalSegmentLength));
                    UVPoint p2 = makeUV(((ix + 1) * _HorizontalSegmentLength), (iy * _VerticalSegmentLength));
                    UVPoint p3 = makeUV((ix * _HorizontalSegmentLength), ((iy + 1) * _VerticalSegmentLength));
                    UVPoint p4 = makeUV(((ix + 1) * _HorizontalSegmentLength), ((iy + 1) * _VerticalSegmentLength));

                    // add this quad to the list
                    Quads.Add(new QuadPoint(p1, p2, p3, p4));
                }
            }
        }

        private UVPoint makeUV(Double p1, Double p2)
        {
            // make a uv point where the uv coordinates are the same as the original coordinates.
            // used for initialisiation
            return new UVPoint(p1, p2, p1, p2);
        }


        private void __render(bool useTmap)
        {
            
            UVPoint p1, p2, p3, p4;
            QuadPoint a;
            double cubeX1 = 0;
            double cubeX2 = 0;
            double cubeX3 = 0;
            double cubeX4 = 0;
            double cubeY1 = 0;
            double cubeY2 = 0;
            double cubeY3 = 0;
            double cubeY4 = 0;
            Panel myCanvas = _targetCanvas;
            int num = 0;
            //l = 1;

            for (num = 0; num < Quads.Count; num++)
            {
                a = Quads[num];
                p1 = a.P1;
                p2 = a.P2;
                p3 = a.P3;
                p4 = a.P4;
                // all texturemappers I've seen use 2 matrices, I couldn't get this to work properly in silverlight
                // this method uses only 1 matrix
                // this particular 1 matrix method uses quads, not triangles. which is sufficient for this panorama viewer
                // changing it to use triangles wasn't neccesary for this, since I only need squares.

                cubeX1 = p1.SX;
                cubeY1 = p1.SY;
                cubeX2 = p2.SX;
                cubeY2 = p2.SY;
                cubeX3 = p4.SX;
                cubeY3 = p4.SY;
                cubeX4 = p3.SX;
                cubeY4 = p3.SY;

                double u1 = p1.X;
                double v1 = p1.Y;
                double u2 = p2.X;
                double v2 = p2.Y;
                double u3 = p4.X;
                double v3 = p4.Y;
                double u4 = p3.X;
                double v4 = p3.Y;


                double stepX = 0;
                double stepY = 0;
                double _segments = _SegmentCount;
                double _segLen = _SourceWidth / _segments;
            //    DebugLogger.debugWrite(



                stepX = Math.Floor(num / _SegmentCount);
                stepY = num % _SegmentCount;
                // DebugLogger.debugWrite("step : " + stepX.ToString() + " " + stepY.ToString());

                if ((cubeX1) < Width && (cubeY1) < Height || (cubeX3) < Width && (cubeY3) < Height || (cubeX2) < Width && (cubeY2) < Height)
                    if ((cubeX1) > -150 && (cubeY1) > -150 || (cubeX3) > -150 && (cubeY3) > -150 || (cubeX2) > -150 && (cubeY2) > -150)
                    {

                        double offX = cubeX1 + (cubeX1 - cubeX2) * stepX + (cubeX2 - cubeX3) * stepY;
                        double offY = cubeY1 + (cubeY1 - cubeY2) * stepX + (cubeY2 - cubeY3) * stepY;

                        /* 20080403 martijn croezen
                         * Flash's matrix and silverlight matrix functions arent 100% compatible
                         * Flash uses concat and invert which aren't present in silverlight
                         * Rendering a transformed imagebrush also is different from flash in that it
                         * does not use the first pixel in the pointlist of a polygon, but uses the top and leftmost pixel as
                         * starting point for drawing the bitmap.
                         * Do a crude check to see which point is the topmost and leftmost pixel, and adjust the offset for the bitmap
                         * to be at the first poly-point.
                         * 
                       */
                        if (cubeX1 <= cubeX2 && cubeX1 <= cubeX3)
                        {
                            //cubeX1 = smallest, offset is correct
                        }
                        else
                        {
                            if (cubeX2 <= cubeX1 && cubeX2 <= cubeX3)
                            {
                                //cubeX2 = smallest, change offset
                                offX = offX + (cubeX1 - cubeX2);
                            }
                            else
                            {
                                //cubeX3 = smallest, change offset
                                offX = offX + (cubeX1 - cubeX3);
                            }
                        }

                        if (cubeY1 <= cubeY2 && cubeY1 <= cubeY3)
                        {
                            //cubeY1 = smallest, offset is correct
                        }
                        else
                        {
                            if (cubeY2 <= cubeY1 && cubeY2 <= cubeY3)
                            {
                                //cubeY2 = smallest, change offset
                                offY = offY + (cubeY1 - cubeY2);
                            }
                            else
                            {
                                //cubeX3 = smallest, change offset
                                offY = offY + (cubeY1 - cubeY3);
                            }
                        }


                        // create the transformation matrix

                        /*
                         * matrix in flash,    matrix in silverlight
                         * matrix.a             matrix.M11                  Scale X
                         * matrix.b             matrix.M12                  Skew Y
                         * matrix.c             matrix.M21                  Skew X
                         * matrix.d             matrix.M22                  Scale Y
                         * matrix.sx            matrix.OffsetX              offset X
                         * matrix.sy            matrix.OffsetY              offset Y
                        */
                        translateMatrix.OffsetX = offX - cubeX1;
                        translateMatrix.OffsetY = offY - cubeY1;
                        translateMatrix.M11 = (cubeX2 - cubeX1) / _segLen; //a  scale X
                        translateMatrix.M12 = (cubeY2 - cubeY1) / _segLen; //b  skew Y
                        translateMatrix.M21 = (cubeX3 - cubeX2) / _segLen; //c  skew X 
                        translateMatrix.M22 = (cubeY3 - cubeY2) / _segLen; //d  scale Y


                        // make the first polygon
                        PointCollection points = new PointCollection();
                        points.Add(new Point((cubeX1), (cubeY1)));
                        points.Add(new Point((cubeX2), (cubeY2)));
                        points.Add(new Point((cubeX3), (cubeY3)));

                        Polygon myPoly = new Polygon();
                        myPoly.Points = points;


                        TransformGroup myTransformGroup = new TransformGroup();
                        MatrixTransform myTranslateTransform = new MatrixTransform();
                        myTranslateTransform.Matrix = translateMatrix;
                        myTransformGroup.Children.Add(myTranslateTransform);

                        // init texture brush
                        if (useTmap)
                        {
                            /* 20080403 martijn croezen
                             * I wanted to store the bitmap per face so i wouldn't have to load it for every polygon
                             * but I had trouble doing this so I reverted back to this
                            */
                            //BitmapImage bitmap = new BitmapImage();
                            /*
                            bitmap.SetSource(_stream);
                             */
                            BitmapImage localBitmap = new BitmapImage(TextureUri);
                            ImageBrush TextureBrush = new ImageBrush();
                            TextureBrush.ImageSource = localBitmap;
                            TextureBrush.Stretch = Stretch.None;
                            TextureBrush.AlignmentX = AlignmentX.Left;
                            TextureBrush.AlignmentY = AlignmentY.Top;

                            // assign the transformation to the imagebrush
                            TextureBrush.Transform = myTransformGroup;
                            myPoly.Fill = TextureBrush;
                        }
                        else
                        {
                            myPoly.Fill = new SolidColorBrush(Color.FromArgb(255, 255, 250, 200));
                        }


                        /* 20080403 martijn croezen
                         * add the polygon to the stage
                         * unfortunately, Silverlight's anti aliasing can't be switched off in 2.0
                         * so we're stuck with unsightly lines between all poly's
                         * Please fix this Microsoft!
                         * 
                        */

                        myCanvas.Children.Add(myPoly);


                    }// end if




                if ((cubeX1) < Width && (cubeY1) < Height || (cubeX3) < Width && (cubeY3) < Height || (cubeX4) < Width && (cubeY4) < Height)
                    if ((cubeX1) > -150 && (cubeY1) > -150 || (cubeX3) > -510 && (cubeY3) > -150 || (cubeX4) > -150 && (cubeY4) > -150)
                    {
                        double offX = cubeX1 + (cubeX4 - cubeX3) * stepX + (cubeX1 - cubeX4) * stepY;
                        double offY = cubeY1 + (cubeY4 - cubeY3) * stepX + (cubeY1 - cubeY4) * stepY;


                        double offsetX = offX;
                        /* 20080403 martijn croezen
                         * Flash's matrix and silverlight matrix functions arent 100% compatible
                         * Flash uses concat and invert which aren't present in silverlight
                         * Rendering a transformed imagebrush also is different from flash in that it
                         * does not use the first pixel in the pointlist of a polygon, but uses the top and leftmost pixel as
                         * starting point for drawing the bitmap.
                         * Do a crude check to see which point is the topmost and leftmost pixel, and adjust the offset for the bitmap
                         * to be at the first poly-point.
                         * 
                       */
                        if (cubeX1 <= cubeX4 && cubeX1 <= cubeX3)
                        {
                            //cubeX1 = smallest, offset is correct
                        }
                        else
                        {
                            if (cubeX4 <= cubeX1 && cubeX4 <= cubeX3)
                            {
                                // cubeX2 is smallest, change offset
                                offX = offX + (cubeX1 - cubeX4);
                            }
                            else
                            {
                                //cubeX3 is smallest, change offset
                                offX = offX + (cubeX1 - cubeX3);
                            }
                        }
                        if (cubeY1 <= cubeY4 && cubeY1 <= cubeY3)
                        {
                            //cubeY1 = smallest, offset is correct
                        }
                        else
                        {
                            if (cubeY4 <= cubeY1 && cubeY4 <= cubeY3)
                            {
                                // cubeY2 is smallest, change offset
                                offY = offY + (cubeY1 - cubeY4);
                            }
                            else
                            {
                                // cubeY3 is smallest, change offset
                                offY = offY + (cubeY1 - cubeY3);
                            }
                        }


                        // create the Transformation matrix (see above for more details)
                        translateMatrix.OffsetX = offX - cubeX1;
                        translateMatrix.OffsetY = offY - cubeY1;

                        translateMatrix.M11 = (cubeX3 - cubeX4) / _segLen; // a  scale X
                        translateMatrix.M12 = (cubeY3 - cubeY4) / _segLen; // b  skew Y
                        translateMatrix.M21 = (cubeX4 - cubeX1) / _segLen; // c  skew X
                        translateMatrix.M22 = (cubeY4 - cubeY1) / _segLen; // d  scale Y




                        double centerX;
                        double centerY;
                        centerX = (cubeX1 + cubeX3 + cubeX4) / 3;
                        centerY = (cubeY1 + cubeY3 + cubeY4) / 3;

                        
                        double angle;
                        angle = Math.Atan2((centerY - cubeY1), (centerX - cubeX1));


                        //// funny detail
                        //// I discovered that when you draw a line from the centre to the corners, it will actually
                        //// extend the stroke to where we want the triangle to stretch out to.
                        //// this is the default StrokeMiterLimit (half of the StrokeThickness by default)
                        //// but we can't reach the coordinates used, so we have to calculate them by hand.
                        //Polyline myLine = new Polyline();
                        //PointCollection linepoints = new PointCollection();
                        //linepoints.Add(new Point(centerX, centerY));
                        //linepoints.Add(new Point(cubeX1, cubeY1));
                        //linepoints.Add(new Point(centerX, centerY));
                        //linepoints.Add(new Point(cubeX4, cubeY4));
                        //linepoints.Add(new Point(centerX, centerY));
                        //linepoints.Add(new Point(cubeX3, cubeY3));
                        //linepoints.Add(new Point(centerX, centerY));
                        //myLine.Points = linepoints;
                        //myLine.Stroke = new SolidColorBrush(Colors.Black);
                        //myLine.StrokeThickness = 10;
                        //// uncomment the next line to see the actual endpoint to which the polyline should've drawn.
                        //// also uncomment the adding of the line to the canvas below.
                        //myLine.StrokeMiterLimit = 0;

                        int overlap = 1;
                        cubeX1 -= Math.Cos(angle) * overlap;
                        cubeY1 -= Math.Sin(angle) * overlap;

                        angle = Math.Atan2((centerY - cubeY4), (centerX - cubeX4));
                        cubeX4 -= Math.Cos(angle) * overlap;
                        cubeY4 -= Math.Sin(angle) * overlap;

                        angle = Math.Atan2((centerY - cubeY3), (centerX - cubeX3));
                        cubeX3 -= Math.Cos(angle) * overlap;
                        cubeY3 -= Math.Sin(angle) * overlap;
                        
                        // create the second polygon
                        PointCollection points = new PointCollection();
                        points.Add(new Point((cubeX1), (cubeY1)));
                        points.Add(new Point((cubeX4), (cubeY4)));
                        points.Add(new Point((cubeX3), (cubeY3)));

                        Polygon myPoly = new Polygon();
                        myPoly.Points = points;


                        TransformGroup myTransformGroup = new TransformGroup();
                        MatrixTransform myTranslateTransform = new MatrixTransform();
                        myTranslateTransform.Matrix = translateMatrix;
                        myTransformGroup.Children.Add(myTranslateTransform);


                        // init texture brush
                        if (useTmap)
                        {
                            BitmapImage localBitmap = new BitmapImage(TextureUri);
                            ImageBrush TextureBrush = new ImageBrush();
                            TextureBrush.ImageSource = localBitmap;
                            TextureBrush.Stretch = Stretch.None;
                            TextureBrush.AlignmentX = AlignmentX.Left;
                            TextureBrush.AlignmentY = AlignmentY.Top;

                            // assign the transformation to the imagebrush
                            TextureBrush.Transform = myTransformGroup;
                            myPoly.Fill = TextureBrush;
                        }
                        else
                        {

                            myPoly.Fill = new SolidColorBrush(Color.FromArgb(255, 255, 200, 200));
                        }
                        
                        myCanvas.Children.Add(myPoly);
                        //also uncomment this line if you want to see the polyline beeing drawn
                        //myCanvas.Children.Add(myLine);
                    }
            }
        }




        /* setTransform
        *
        * @param x0 Number the horizontal coordinate of the first point
        * @param y0 Number the vertical coordinate of the first point	
        * @param x1 Number the horizontal coordinate of the second point
        * @param y1 Number the vertical coordinate of the second point	
        * @param x2 Number the horizontal coordinate of the third point
        * @param y2 Number the vertical coordinate of the third point	
        * @param x3 Number the horizontal coordinate of the fourth point
        * @param y3 Number the vertical coordinate of the fourth point	 
        *
        * @description : Distort the bitmap to ajust it to those points.
        */

        public void setTransform(double X0, double Y0, double X1, double Y1, double X2, double Y2, double X3, double Y3, Boolean useTmap)
        {
            int num = Quads.Count;
            while (num-- > 0)
            {
                Quads[num].P1 = transformPoint(Quads[num].P1, X0, Y0, X1, Y1, X2, Y2, X3, Y3);
                Quads[num].P2 = transformPoint(Quads[num].P2, X0, Y0, X1, Y1, X2, Y2, X3, Y3);
                Quads[num].P3 = transformPoint(Quads[num].P3, X0, Y0, X1, Y1, X2, Y2, X3, Y3);
                Quads[num].P4 = transformPoint(Quads[num].P4, X0, Y0, X1, Y1, X2, Y2, X3, Y3);
            }
            __render( useTmap);
        }

        private UVPoint transformPoint(UVPoint UVPoint, double X0, double Y0, double X1, double Y1, double X2, double Y2, double X3, double Y3)
        {
            // transform the destination (sx,sy) coordinates for this point according to its position in the face
            UVPoint result = new UVPoint(UVPoint.X, UVPoint.Y, UVPoint.SX, UVPoint.SY);

            double w = _SourceWidth;
            double h = _SourceHeight;
            double dx30 = X3 - X0;
            double dy30 = Y3 - Y0;
            double dx21 = X2 - X1;
            double dy21 = Y2 - Y1;

            double gx = (UVPoint.X - _XMin) / w;
            double gy = (UVPoint.Y - _YMin) / h;
            double bx = X0 + gy * (dx30);
            double by = Y0 + gy * (dy30);
            result.SX = (bx + gx * ((X1 + gy * (dx21)) - bx));
            result.SY = (by + gx * ((Y1 + gy * (dy21)) - by));
            return (result);
        }
    }
}
